home *** CD-ROM | disk | FTP | other *** search
/ PC Advisor 2010 April / PCA177.iso / ESSENTIALS / Firefox Setup.exe / nonlocalized / components / FeedWriter.js < prev    next >
Encoding:
Text File  |  2009-07-15  |  48.5 KB  |  1,396 lines

  1. //@line 42 "e:\builds\moz2_slave\win32_build\build\browser\components\feeds\src\FeedWriter.js"
  2.  
  3. const Cc = Components.classes;
  4. const Ci = Components.interfaces;
  5. const Cr = Components.results;
  6. const Cu = Components.utils;
  7.  
  8. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  9.  
  10. function LOG(str) {
  11.   var prefB = Cc["@mozilla.org/preferences-service;1"].
  12.               getService(Ci.nsIPrefBranch);
  13.  
  14.   var shouldLog = false;
  15.   try {
  16.     shouldLog = prefB.getBoolPref("feeds.log");
  17.   } 
  18.   catch (ex) {
  19.   }
  20.  
  21.   if (shouldLog)
  22.     dump("*** Feeds: " + str + "\n");
  23. }
  24.  
  25. /**
  26.  * Wrapper function for nsIIOService::newURI.
  27.  * @param aURLSpec
  28.  *        The URL string from which to create an nsIURI.
  29.  * @returns an nsIURI object, or null if the creation of the URI failed.
  30.  */
  31. function makeURI(aURLSpec, aCharset) {
  32.   var ios = Cc["@mozilla.org/network/io-service;1"].
  33.             getService(Ci.nsIIOService);
  34.   try {
  35.     return ios.newURI(aURLSpec, aCharset, null);
  36.   } catch (ex) { }
  37.  
  38.   return null;
  39. }
  40.  
  41. const XML_NS = "http://www.w3.org/XML/1998/namespace"
  42. const HTML_NS = "http://www.w3.org/1999/xhtml";
  43. const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  44. const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
  45. const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
  46. const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
  47. const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties";
  48. const SUBSCRIBE_PAGE_URI = "chrome://browser/content/feeds/subscribe.xhtml";
  49.  
  50. const PREF_SELECTED_APP = "browser.feeds.handlers.application";
  51. const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
  52. const PREF_SELECTED_ACTION = "browser.feeds.handler";
  53. const PREF_SELECTED_READER = "browser.feeds.handler.default";
  54.  
  55. const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
  56. const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
  57. const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
  58. const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
  59.  
  60. const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
  61. const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
  62. const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
  63. const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
  64.  
  65. const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
  66.  
  67. const TITLE_ID = "feedTitleText";
  68. const SUBTITLE_ID = "feedSubtitleText";
  69.  
  70. function getPrefAppForType(t) {
  71.   switch (t) {
  72.     case Ci.nsIFeed.TYPE_VIDEO:
  73.       return PREF_VIDEO_SELECTED_APP;
  74.  
  75.     case Ci.nsIFeed.TYPE_AUDIO:
  76.       return PREF_AUDIO_SELECTED_APP;
  77.  
  78.     default:
  79.       return PREF_SELECTED_APP;
  80.   }
  81. }
  82.  
  83. function getPrefWebForType(t) {
  84.   switch (t) {
  85.     case Ci.nsIFeed.TYPE_VIDEO:
  86.       return PREF_VIDEO_SELECTED_WEB;
  87.  
  88.     case Ci.nsIFeed.TYPE_AUDIO:
  89.       return PREF_AUDIO_SELECTED_WEB;
  90.  
  91.     default:
  92.       return PREF_SELECTED_WEB;
  93.   }
  94. }
  95.  
  96. function getPrefActionForType(t) {
  97.   switch (t) {
  98.     case Ci.nsIFeed.TYPE_VIDEO:
  99.       return PREF_VIDEO_SELECTED_ACTION;
  100.  
  101.     case Ci.nsIFeed.TYPE_AUDIO:
  102.       return PREF_AUDIO_SELECTED_ACTION;
  103.  
  104.     default:
  105.       return PREF_SELECTED_ACTION;
  106.   }
  107. }
  108.  
  109. function getPrefReaderForType(t) {
  110.   switch (t) {
  111.     case Ci.nsIFeed.TYPE_VIDEO:
  112.       return PREF_VIDEO_SELECTED_READER;
  113.  
  114.     case Ci.nsIFeed.TYPE_AUDIO:
  115.       return PREF_AUDIO_SELECTED_READER;
  116.  
  117.     default:
  118.       return PREF_SELECTED_READER;
  119.   }
  120. }
  121.  
  122. /**
  123.  * Converts a number of bytes to the appropriate unit that results in a
  124.  * number that needs fewer than 4 digits
  125.  *
  126.  * @return a pair: [new value with 3 sig. figs., its unit]
  127.   */
  128. function convertByteUnits(aBytes) {
  129.   var units = ["bytes", "kilobyte", "megabyte", "gigabyte"];
  130.   let unitIndex = 0;
  131.  
  132.   // convert to next unit if it needs 4 digits (after rounding), but only if
  133.   // we know the name of the next unit
  134.   while ((aBytes >= 999.5) && (unitIndex < units.length - 1)) {
  135.     aBytes /= 1024;
  136.     unitIndex++;
  137.   }
  138.  
  139.   // Get rid of insignificant bits by truncating to 1 or 0 decimal points
  140.   // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
  141.   aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) ? 1 : 0);
  142.  
  143.   return [aBytes, units[unitIndex]];
  144. }
  145.  
  146. function FeedWriter() {}
  147. FeedWriter.prototype = {
  148.   _mimeSvc      : Cc["@mozilla.org/mime;1"].
  149.                   getService(Ci.nsIMIMEService),
  150.  
  151.   _getPropertyAsBag: function FW__getPropertyAsBag(container, property) {
  152.     return container.fields.getProperty(property).
  153.                      QueryInterface(Ci.nsIPropertyBag2);
  154.   },
  155.  
  156.   _getPropertyAsString: function FW__getPropertyAsString(container, property) {
  157.     try {
  158.       return container.fields.getPropertyAsAString(property);
  159.     }
  160.     catch (e) {
  161.     }
  162.     return "";
  163.   },
  164.  
  165.   _setContentText: function FW__setContentText(id, text) {
  166.     this._contentSandbox.element = this._document.getElementById(id);
  167.     this._contentSandbox.textNode = this._document.createTextNode(text);
  168.     var codeStr =
  169.       "while (element.hasChildNodes()) " +
  170.       "  element.removeChild(element.firstChild);" +
  171.       "element.appendChild(textNode);";
  172.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  173.     this._contentSandbox.element = null;
  174.     this._contentSandbox.textNode = null;
  175.   },
  176.  
  177.   /**
  178.    * Safely sets the href attribute on an anchor tag, providing the URI 
  179.    * specified can be loaded according to rules. 
  180.    * @param   element
  181.    *          The element to set a URI attribute on
  182.    * @param   attribute
  183.    *          The attribute of the element to set the URI to, e.g. href or src
  184.    * @param   uri
  185.    *          The URI spec to set as the href
  186.    */
  187.   _safeSetURIAttribute: 
  188.   function FW__safeSetURIAttribute(element, attribute, uri) {
  189.     var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
  190.                  getService(Ci.nsIScriptSecurityManager);    
  191.     const flags = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
  192.     try {
  193.       secman.checkLoadURIStrWithPrincipal(this._feedPrincipal, uri, flags);
  194.       // checkLoadURIStrWithPrincipal will throw if the link URI should not be
  195.       // loaded, either because our feedURI isn't allowed to load it or per
  196.       // the rules specified in |flags|, so we'll never "linkify" the link...
  197.     }
  198.     catch (e) {
  199.       // Not allowed to load this link because secman.checkLoadURIStr threw
  200.       return;
  201.     }
  202.  
  203.     this._contentSandbox.element = element;
  204.     this._contentSandbox.uri = uri;
  205.     var codeStr = "element.setAttribute('" + attribute + "', uri);";
  206.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  207.   },
  208.  
  209.   /**
  210.    * Use this sandbox to run any dom manipulation code on nodes which
  211.    * are already inserted into the content document.
  212.    */
  213.   __contentSandbox: null,
  214.   get _contentSandbox() {
  215.     if (!this.__contentSandbox)
  216.       this.__contentSandbox = new Cu.Sandbox(this._window);
  217.  
  218.     return this.__contentSandbox;
  219.   },
  220.  
  221.   /**
  222.    * Calls doCommand for a the given XUL element within the context of the
  223.    * content document.
  224.    *
  225.    * @param aElement
  226.    *        the XUL element to call doCommand() on.
  227.    */
  228.   _safeDoCommand: function FW___safeDoCommand(aElement) {
  229.     this._contentSandbox.element = aElement;
  230.     Cu.evalInSandbox("element.doCommand();", this._contentSandbox);
  231.     this._contentSandbox.element = null;
  232.   },
  233.  
  234.   __faviconService: null,
  235.   get _faviconService() {
  236.     if (!this.__faviconService)
  237.       this.__faviconService = Cc["@mozilla.org/browser/favicon-service;1"].
  238.                               getService(Ci.nsIFaviconService);
  239.  
  240.     return this.__faviconService;
  241.   },
  242.  
  243.   __bundle: null,
  244.   get _bundle() {
  245.     if (!this.__bundle) {
  246.       this.__bundle = Cc["@mozilla.org/intl/stringbundle;1"].
  247.                       getService(Ci.nsIStringBundleService).
  248.                       createBundle(URI_BUNDLE);
  249.     }
  250.     return this.__bundle;
  251.   },
  252.  
  253.   _getFormattedString: function FW__getFormattedString(key, params) {
  254.     return this._bundle.formatStringFromName(key, params, params.length);
  255.   },
  256.   
  257.   _getString: function FW__getString(key) {
  258.     return this._bundle.GetStringFromName(key);
  259.   },
  260.  
  261.   /* Magic helper methods to be used instead of xbl properties */
  262.   _getSelectedItemFromMenulist: function FW__getSelectedItemFromList(aList) {
  263.     var node = aList.firstChild.firstChild;
  264.     while (node) {
  265.       if (node.localName == "menuitem" && node.getAttribute("selected") == "true")
  266.         return node;
  267.  
  268.       node = node.nextSibling;
  269.     }
  270.  
  271.     return null;
  272.   },
  273.  
  274.   _setCheckboxCheckedState: function FW__setCheckboxCheckedState(aCheckbox, aValue) {
  275.     // see checkbox.xml, xbl bindings are not applied within the sandbox!
  276.     this._contentSandbox.checkbox = aCheckbox;
  277.     var codeStr;
  278.     var change = (aValue != (aCheckbox.getAttribute('checked') == 'true'));
  279.     if (aValue)
  280.       codeStr = "checkbox.setAttribute('checked', 'true'); ";
  281.     else
  282.       codeStr = "checkbox.removeAttribute('checked'); ";
  283.  
  284.     if (change) {
  285.       this._contentSandbox.document = this._document;
  286.       codeStr += "var event = document.createEvent('Events'); " +
  287.                  "event.initEvent('CheckboxStateChange', true, true);" +
  288.                  "checkbox.dispatchEvent(event);"
  289.     }
  290.  
  291.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  292.   },
  293.  
  294.    /**
  295.    * Returns a date suitable for displaying in the feed preview. 
  296.    * If the date cannot be parsed, the return value is "false".
  297.    * @param   dateString
  298.    *          A date as extracted from a feed entry. (entry.updated)
  299.    */
  300.   _parseDate: function FW__parseDate(dateString) {
  301.     // Convert the date into the user's local time zone
  302.     dateObj = new Date(dateString);
  303.  
  304.     // Make sure the date we're given is valid.
  305.     if (!dateObj.getTime())
  306.       return false;
  307.  
  308.     var dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
  309.                       getService(Ci.nsIScriptableDateFormat);
  310.     return dateService.FormatDateTime("", dateService.dateFormatLong, dateService.timeFormatNoSeconds,
  311.                                       dateObj.getFullYear(), dateObj.getMonth()+1, dateObj.getDate(),
  312.                                       dateObj.getHours(), dateObj.getMinutes(), dateObj.getSeconds());
  313.   },
  314.  
  315.   /**
  316.    * Returns the feed type.
  317.    */
  318.   __feedType: null,
  319.   _getFeedType: function FW__getFeedType() {
  320.     if (this.__feedType != null)
  321.       return this.__feedType;
  322.  
  323.     try {
  324.       // grab the feed because it's got the feed.type in it.
  325.       var container = this._getContainer();
  326.       var feed = container.QueryInterface(Ci.nsIFeed);
  327.       this.__feedType = feed.type;
  328.       return feed.type;
  329.     } catch (ex) { }
  330.  
  331.     return Ci.nsIFeed.TYPE_FEED;
  332.   },
  333.  
  334.   /**
  335.    * Maps a feed type to a maybe-feed mimetype.
  336.    */
  337.   _getMimeTypeForFeedType: function FW__getMimeTypeForFeedType() {
  338.     switch (this._getFeedType()) {
  339.       case Ci.nsIFeed.TYPE_VIDEO:
  340.         return TYPE_MAYBE_VIDEO_FEED;
  341.  
  342.       case Ci.nsIFeed.TYPE_AUDIO:
  343.         return TYPE_MAYBE_AUDIO_FEED;
  344.  
  345.       default:
  346.         return TYPE_MAYBE_FEED;
  347.     }
  348.   },
  349.  
  350.   /**
  351.    * Writes the feed title into the preview document.
  352.    * @param   container
  353.    *          The feed container
  354.    */
  355.   _setTitleText: function FW__setTitleText(container) {
  356.     if (container.title) {
  357.       var title = container.title.plainText();
  358.       this._setContentText(TITLE_ID, title);
  359.       this._contentSandbox.document = this._document;
  360.       this._contentSandbox.title = title;
  361.       var codeStr = "document.title = title;"
  362.       Cu.evalInSandbox(codeStr, this._contentSandbox);
  363.     }
  364.  
  365.     var feed = container.QueryInterface(Ci.nsIFeed);
  366.     if (feed && feed.subtitle)
  367.       this._setContentText(SUBTITLE_ID, container.subtitle.plainText());
  368.   },
  369.  
  370.   /**
  371.    * Writes the title image into the preview document if one is present.
  372.    * @param   container
  373.    *          The feed container
  374.    */
  375.   _setTitleImage: function FW__setTitleImage(container) {
  376.     try {
  377.       var parts = container.image;
  378.       
  379.       // Set up the title image (supplied by the feed)
  380.       var feedTitleImage = this._document.getElementById("feedTitleImage");
  381.       this._safeSetURIAttribute(feedTitleImage, "src", 
  382.                                 parts.getPropertyAsAString("url"));
  383.  
  384.       // Set up the title image link
  385.       var feedTitleLink = this._document.getElementById("feedTitleLink");
  386.  
  387.       var titleText = this._getFormattedString("linkTitleTextFormat", 
  388.                                                [parts.getPropertyAsAString("title")]);
  389.       this._contentSandbox.feedTitleLink = feedTitleLink;
  390.       this._contentSandbox.titleText = titleText;
  391.       this._contentSandbox.feedTitleText = this._document.getElementById("feedTitleText");
  392.       this._contentSandbox.titleImageWidth = parseInt(parts.getPropertyAsAString("width")) + 15;
  393.  
  394.       // Fix the margin on the main title, so that the image doesn't run over
  395.       // the underline
  396.       var codeStr = "feedTitleLink.setAttribute('title', titleText); " +
  397.                     "feedTitleText.style.marginRight = titleImageWidth + 'px';";
  398.       Cu.evalInSandbox(codeStr, this._contentSandbox);
  399.       this._contentSandbox.feedTitleLink = null;
  400.       this._contentSandbox.titleText = null;
  401.       this._contentSandbox.feedTitleText = null;
  402.       this._contentSandbox.titleImageWidth = null;
  403.  
  404.       this._safeSetURIAttribute(feedTitleLink, "href", 
  405.                                 parts.getPropertyAsAString("link"));
  406.     }
  407.     catch (e) {
  408.       LOG("Failed to set Title Image (this is benign): " + e);
  409.     }
  410.   },
  411.  
  412.   /**
  413.    * Writes all entries contained in the feed.
  414.    * @param   container
  415.    *          The container of entries in the feed
  416.    */
  417.   _writeFeedContent: function FW__writeFeedContent(container) {
  418.     // Build the actual feed content
  419.     var feed = container.QueryInterface(Ci.nsIFeed);
  420.     if (feed.items.length == 0)
  421.       return;
  422.  
  423.     this._contentSandbox.feedContent =
  424.       this._document.getElementById("feedContent");
  425.  
  426.     for (var i = 0; i < feed.items.length; ++i) {
  427.       var entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
  428.       entry.QueryInterface(Ci.nsIFeedContainer);
  429.  
  430.       var entryContainer = this._document.createElementNS(HTML_NS, "div");
  431.       entryContainer.className = "entry";
  432.  
  433.       // If the entry has a title, make it a link
  434.       if (entry.title) {
  435.         var a = this._document.createElementNS(HTML_NS, "a");
  436.         a.appendChild(this._document.createTextNode(entry.title.plainText()));
  437.  
  438.         // Entries are not required to have links, so entry.link can be null.
  439.         if (entry.link)
  440.           this._safeSetURIAttribute(a, "href", entry.link.spec);
  441.  
  442.         var title = this._document.createElementNS(HTML_NS, "h3");
  443.         title.appendChild(a);
  444.  
  445.         var lastUpdated = this._parseDate(entry.updated);
  446.         if (lastUpdated) {
  447.           var dateDiv = this._document.createElementNS(HTML_NS, "div");
  448.           dateDiv.className = "lastUpdated";
  449.           dateDiv.textContent = lastUpdated;
  450.           title.appendChild(dateDiv);
  451.         }
  452.  
  453.         entryContainer.appendChild(title);
  454.       }
  455.  
  456.       var body = this._document.createElementNS(HTML_NS, "div");
  457.       var summary = entry.summary || entry.content;
  458.       var docFragment = null;
  459.       if (summary) {
  460.         if (summary.base)
  461.           body.setAttributeNS(XML_NS, "base", summary.base.spec);
  462.         else
  463.           LOG("no base?");
  464.         docFragment = summary.createDocumentFragment(body);
  465.         if (docFragment)
  466.           body.appendChild(docFragment);
  467.  
  468.         // If the entry doesn't have a title, append a # permalink
  469.         // See http://scripting.com/rss.xml for an example
  470.         if (!entry.title && entry.link) {
  471.           var a = this._document.createElementNS(HTML_NS, "a");
  472.           a.appendChild(this._document.createTextNode("#"));
  473.           this._safeSetURIAttribute(a, "href", entry.link.spec);
  474.           body.appendChild(this._document.createTextNode(" "));
  475.           body.appendChild(a);
  476.         }
  477.  
  478.       }
  479.       body.className = "feedEntryContent";
  480.       entryContainer.appendChild(body);
  481.  
  482.       if (entry.enclosures && entry.enclosures.length > 0) {
  483.         var enclosuresDiv = this._buildEnclosureDiv(entry);
  484.         entryContainer.appendChild(enclosuresDiv);
  485.       }
  486.  
  487.       this._contentSandbox.entryContainer = entryContainer;
  488.       this._contentSandbox.clearDiv =
  489.         this._document.createElementNS(HTML_NS, "div");
  490.       this._contentSandbox.clearDiv.style.clear = "both";
  491.       
  492.       var codeStr = "feedContent.appendChild(entryContainer); " +
  493.                      "feedContent.appendChild(clearDiv);"
  494.       Cu.evalInSandbox(codeStr, this._contentSandbox);
  495.     }
  496.  
  497.     this._contentSandbox.feedContent = null;
  498.     this._contentSandbox.entryContainer = null;
  499.     this._contentSandbox.clearDiv = null;
  500.   },
  501.  
  502.   /**
  503.    * Takes a url to a media item and returns the best name it can come up with.
  504.    * Frequently this is the filename portion (e.g. passing in 
  505.    * http://example.com/foo.mpeg would return "foo.mpeg"), but in more complex
  506.    * cases, this will return the entire url (e.g. passing in
  507.    * http://example.com/somedirectory/ would return 
  508.    * http://example.com/somedirectory/).
  509.    * @param aURL
  510.    *        The URL string from which to create a display name
  511.    * @returns a string
  512.    */
  513.   _getURLDisplayName: function FW__getURLDisplayName(aURL) {
  514.     var url = makeURI(aURL);
  515.     url.QueryInterface(Ci.nsIURL);
  516.     if (url == null || url.fileName.length == 0)
  517.       return aURL;
  518.  
  519.     return decodeURI(url.fileName);
  520.   },
  521.  
  522.   /**
  523.    * Takes a FeedEntry with enclosures, generates the HTML code to represent
  524.    * them, and returns that.
  525.    * @param   entry
  526.    *          FeedEntry with enclosures
  527.    * @returns element
  528.    */
  529.   _buildEnclosureDiv: function FW__buildEnclosureDiv(entry) {
  530.     var enclosuresDiv = this._document.createElementNS(HTML_NS, "div");
  531.     enclosuresDiv.className = "enclosures";
  532.  
  533.     enclosuresDiv.appendChild(this._document.createTextNode(this._getString("mediaLabel")));
  534.  
  535.     var roundme = function(n) {
  536.       return (Math.round(n * 100) / 100).toLocaleString();
  537.     }
  538.  
  539.     for (var i_enc = 0; i_enc < entry.enclosures.length; ++i_enc) {
  540.       var enc = entry.enclosures.queryElementAt(i_enc, Ci.nsIWritablePropertyBag2);
  541.  
  542.       if (!(enc.hasKey("url"))) 
  543.         continue;
  544.  
  545.       var enclosureDiv = this._document.createElementNS(HTML_NS, "div");
  546.       enclosureDiv.setAttribute("class", "enclosure");
  547.  
  548.       var mozicon = "moz-icon://.txt?size=16";
  549.       var type_text = null;
  550.       var size_text = null;
  551.  
  552.       if (enc.hasKey("type")) {
  553.         type_text = enc.get("type");
  554.         try {
  555.           var handlerInfoWrapper = this._mimeSvc.getFromTypeAndExtension(enc.get("type"), null);
  556.  
  557.           if (handlerInfoWrapper)
  558.             type_text = handlerInfoWrapper.description;
  559.  
  560.           if  (type_text && type_text.length > 0)
  561.             mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type");
  562.  
  563.         } catch (ex) { }
  564.  
  565.       }
  566.  
  567.       if (enc.hasKey("length") && /^[0-9]+$/.test(enc.get("length"))) {
  568.         var enc_size = convertByteUnits(parseInt(enc.get("length")));
  569.  
  570.         var size_text = this._getFormattedString("enclosureSizeText", 
  571.                              [enc_size[0], this._getString(enc_size[1])]);
  572.       }
  573.  
  574.       var iconimg = this._document.createElementNS(HTML_NS, "img");
  575.       iconimg.setAttribute("src", mozicon);
  576.       iconimg.setAttribute("class", "type-icon");
  577.       enclosureDiv.appendChild(iconimg);
  578.  
  579.       enclosureDiv.appendChild(this._document.createTextNode( " " ));
  580.  
  581.       var enc_href = this._document.createElementNS(HTML_NS, "a");
  582.       enc_href.appendChild(this._document.createTextNode(this._getURLDisplayName(enc.get("url"))));
  583.       this._safeSetURIAttribute(enc_href, "href", enc.get("url"));
  584.       enclosureDiv.appendChild(enc_href);
  585.  
  586.       if (type_text && size_text)
  587.         enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ", " + size_text + ")"));
  588.  
  589.       else if (type_text) 
  590.         enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ")"))
  591.  
  592.       else if (size_text)
  593.         enclosureDiv.appendChild(this._document.createTextNode( " (" + size_text + ")"))
  594.  
  595.       enclosuresDiv.appendChild(enclosureDiv);
  596.     }
  597.  
  598.     return enclosuresDiv;
  599.   },
  600.  
  601.   /**
  602.    * Gets a valid nsIFeedContainer object from the parsed nsIFeedResult.
  603.    * Displays error information if there was one.
  604.    * @param   result
  605.    *          The parsed feed result
  606.    * @returns A valid nsIFeedContainer object containing the contents of
  607.    *          the feed.
  608.    */
  609.   _getContainer: function FW__getContainer(result) {
  610.     var feedService = 
  611.         Cc["@mozilla.org/browser/feeds/result-service;1"].
  612.         getService(Ci.nsIFeedResultService);
  613.  
  614.     try {
  615.       var result = 
  616.         feedService.getFeedResult(this._getOriginalURI(this._window));
  617.     }
  618.     catch (e) {
  619.       LOG("Subscribe Preview: feed not available?!");
  620.     }
  621.     
  622.     if (result.bozo) {
  623.       LOG("Subscribe Preview: feed result is bozo?!");
  624.     }
  625.  
  626.     try {
  627.       var container = result.doc;
  628.     }
  629.     catch (e) {
  630.       LOG("Subscribe Preview: no result.doc? Why didn't the original reload?");
  631.       return null;
  632.     }
  633.     return container;
  634.   },
  635.  
  636.   /**
  637.    * Get the human-readable display name of a file. This could be the 
  638.    * application name.
  639.    * @param   file
  640.    *          A nsIFile to look up the name of
  641.    * @returns The display name of the application represented by the file.
  642.    */
  643.   _getFileDisplayName: function FW__getFileDisplayName(file) {
  644. //@line 685 "e:\builds\moz2_slave\win32_build\build\browser\components\feeds\src\FeedWriter.js"
  645.     if (file instanceof Ci.nsILocalFileWin) {
  646.       try {
  647.         return file.getVersionInfoField("FileDescription");
  648.       }
  649.       catch (e) {
  650.       }
  651.     }
  652. //@line 702 "e:\builds\moz2_slave\win32_build\build\browser\components\feeds\src\FeedWriter.js"
  653.     var ios = 
  654.         Cc["@mozilla.org/network/io-service;1"].
  655.         getService(Ci.nsIIOService);
  656.     var url = ios.newFileURI(file).QueryInterface(Ci.nsIURL);
  657.     return url.fileName;
  658.   },
  659.  
  660.   /**
  661.    * Get moz-icon url for a file
  662.    * @param   file
  663.    *          A nsIFile object for which the moz-icon:// is returned
  664.    * @returns moz-icon url of the given file as a string
  665.    */
  666.   _getFileIconURL: function FW__getFileIconURL(file) {
  667.     var ios = Cc["@mozilla.org/network/io-service;1"].
  668.               getService(Components.interfaces.nsIIOService);
  669.     var fph = ios.getProtocolHandler("file")
  670.                  .QueryInterface(Ci.nsIFileProtocolHandler);
  671.     var urlSpec = fph.getURLSpecFromFile(file);
  672.     return "moz-icon://" + urlSpec + "?size=16";
  673.   },
  674.  
  675.   /**
  676.    * Helper method to set the selected application and system default
  677.    * reader menuitems details from a file object
  678.    *   @param aMenuItem
  679.    *          The menuitem on which the attributes should be set
  680.    *   @param aFile
  681.    *          The menuitem's associated file
  682.    */
  683.   _initMenuItemWithFile: function(aMenuItem, aFile) {
  684.     this._contentSandbox.menuitem = aMenuItem;
  685.     this._contentSandbox.label = this._getFileDisplayName(aFile);
  686.     this._contentSandbox.image = this._getFileIconURL(aFile);
  687.     var codeStr = "menuitem.setAttribute('label', label); " +
  688.                   "menuitem.setAttribute('image', image);"
  689.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  690.   },
  691.  
  692.   /**
  693.    * Displays a prompt from which the user may choose a (client) feed reader.
  694.    * @return - true if a feed reader was selected, false otherwise.
  695.    */
  696.   _chooseClientApp: function FW__chooseClientApp() {
  697.     try {
  698.       var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  699.       fp.init(this._window,
  700.               this._getString("chooseApplicationDialogTitle"),
  701.               Ci.nsIFilePicker.modeOpen);
  702.       fp.appendFilters(Ci.nsIFilePicker.filterApps);
  703.  
  704.       if (fp.show() == Ci.nsIFilePicker.returnOK) {
  705.         this._selectedApp = fp.file;
  706.         if (this._selectedApp) {
  707.           // XXXben - we need to compare this with the running instance executable
  708.           //          just don't know how to do that via script...
  709.           // XXXmano TBD: can probably add this to nsIShellService
  710. //@line 760 "e:\builds\moz2_slave\win32_build\build\browser\components\feeds\src\FeedWriter.js"
  711.           if (fp.file.leafName != "firefox.exe") {
  712. //@line 768 "e:\builds\moz2_slave\win32_build\build\browser\components\feeds\src\FeedWriter.js"
  713.             this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
  714.                                        this._selectedApp);
  715.  
  716.             // Show and select the selected application menuitem
  717.             var codeStr = "selectedAppMenuItem.hidden = false;" +
  718.                           "selectedAppMenuItem.doCommand();"
  719.             Cu.evalInSandbox(codeStr, this._contentSandbox);
  720.             return true;
  721.           }
  722.         }
  723.       }
  724.     }
  725.     catch(ex) { }
  726.  
  727.     return false;
  728.   },
  729.  
  730.   _setAlwaysUseCheckedState: function FW__setAlwaysUseCheckedState(feedType) {
  731.     var checkbox = this._document.getElementById("alwaysUse");
  732.     if (checkbox) {
  733.       var alwaysUse = false;
  734.       try {
  735.         var prefs = Cc["@mozilla.org/preferences-service;1"].
  736.                     getService(Ci.nsIPrefBranch);
  737.         if (prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
  738.           alwaysUse = true;
  739.       }
  740.       catch(ex) { }
  741.       this._setCheckboxCheckedState(checkbox, alwaysUse);
  742.     }
  743.   },
  744.  
  745.   _setSubscribeUsingLabel: function FW__setSubscribeUsingLabel() {
  746.     var stringLabel = "subscribeFeedUsing";
  747.     switch (this._getFeedType()) {
  748.       case Ci.nsIFeed.TYPE_VIDEO:
  749.         stringLabel = "subscribeVideoPodcastUsing";
  750.         break;
  751.  
  752.       case Ci.nsIFeed.TYPE_AUDIO:
  753.         stringLabel = "subscribeAudioPodcastUsing";
  754.         break;
  755.     }
  756.  
  757.     this._contentSandbox.subscribeUsing =
  758.       this._document.getElementById("subscribeUsingDescription");
  759.     this._contentSandbox.label = this._getString(stringLabel);
  760.     var codeStr = "subscribeUsing.setAttribute('value', label);"
  761.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  762.   },
  763.  
  764.   _setAlwaysUseLabel: function FW__setAlwaysUseLabel() {
  765.     var checkbox = this._document.getElementById("alwaysUse");
  766.     if (checkbox) {
  767.       var handlersMenuList = this._document.getElementById("handlersMenuList");
  768.       if (handlersMenuList) {
  769.         var handlerName = this._getSelectedItemFromMenulist(handlersMenuList)
  770.                               .getAttribute("label");
  771.         var stringLabel = "alwaysUseForFeeds";
  772.         switch (this._getFeedType()) {
  773.           case Ci.nsIFeed.TYPE_VIDEO:
  774.             stringLabel = "alwaysUseForVideoPodcasts";
  775.             break;
  776.  
  777.           case Ci.nsIFeed.TYPE_AUDIO:
  778.             stringLabel = "alwaysUseForAudioPodcasts";
  779.             break;
  780.         }
  781.  
  782.         this._contentSandbox.checkbox = checkbox;
  783.         this._contentSandbox.label = this._getFormattedString(stringLabel, [handlerName]);
  784.         
  785.         var codeStr = "checkbox.setAttribute('label', label);";
  786.         Cu.evalInSandbox(codeStr, this._contentSandbox);
  787.       }
  788.     }
  789.   },
  790.  
  791.   // nsIDomEventListener
  792.   handleEvent: function(event) {
  793.     // see comments in init()
  794.     event = new XPCNativeWrapper(event);
  795.     if (event.target.ownerDocument != this._document) {
  796.       LOG("FeedWriter.handleEvent: Someone passed the feed writer as a listener to the events of another document!");
  797.       return;
  798.     }
  799.  
  800.     if (event.type == "command") {
  801.       switch (event.target.id) {
  802.         case "subscribeButton":
  803.           this.subscribe();
  804.           break;
  805.         case "chooseApplicationMenuItem":
  806.           /* Bug 351263: Make sure to not steal focus if the "Choose
  807.            * Application" item is being selected with the keyboard. We do this
  808.            * by ignoring command events while the dropdown is closed (user
  809.            * arrowing through the combobox), but handling them while the
  810.            * combobox dropdown is open (user pressed enter when an item was
  811.            * selected). If we don't show the filepicker here, it will be shown
  812.            * when clicking "Subscribe Now".
  813.            */
  814.           var popupbox = this._document.getElementById("handlersMenuList")
  815.                              .firstChild.boxObject;
  816.           popupbox.QueryInterface(Components.interfaces.nsIPopupBoxObject);
  817.           if (popupbox.popupState == "hiding" && !this._chooseClientApp()) {
  818.             // Select the (per-prefs) selected handler if no application was
  819.             // selected
  820.             this._setSelectedHandler(this._getFeedType());
  821.           }
  822.           break;
  823.         default:
  824.           this._setAlwaysUseLabel();
  825.       }
  826.     }
  827.   },
  828.  
  829.   _setSelectedHandler: function FW__setSelectedHandler(feedType) {
  830.     var prefs =   
  831.         Cc["@mozilla.org/preferences-service;1"].
  832.         getService(Ci.nsIPrefBranch);
  833.  
  834.     var handler = "bookmarks";
  835.     try {
  836.       handler = prefs.getCharPref(getPrefReaderForType(feedType));
  837.     }
  838.     catch (ex) { }
  839.  
  840.     switch (handler) {
  841.       case "web": {
  842.         var handlersMenuList = this._document.getElementById("handlersMenuList");
  843.         if (handlersMenuList) {
  844.           var url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data;
  845.           var handlers =
  846.             handlersMenuList.getElementsByAttribute("webhandlerurl", url);
  847.           if (handlers.length == 0) {
  848.             LOG("FeedWriter._setSelectedHandler: selected web handler isn't in the menulist")
  849.             return;
  850.           }
  851.  
  852.           this._safeDoCommand(handlers[0]);
  853.         }
  854.         break;
  855.       }
  856.       case "client": {
  857.         try {
  858.           this._selectedApp =
  859.             prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile);
  860.         }
  861.         catch(ex) {
  862.           this._selectedApp = null;
  863.         }
  864.  
  865.         if (this._selectedApp) {
  866.           this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
  867.                                      this._selectedApp);
  868.           var codeStr = "selectedAppMenuItem.hidden = false; " +
  869.                         "selectedAppMenuItem.doCommand(); ";
  870.  
  871.           // Only show the default reader menuitem if the default reader
  872.           // isn't the selected application
  873.           if (this._defaultSystemReader) {
  874.             var shouldHide =
  875.               this._defaultSystemReader.path == this._selectedApp.path;
  876.             codeStr += "defaultHandlerMenuItem.hidden = " + shouldHide + ";"
  877.           }
  878.           Cu.evalInSandbox(codeStr, this._contentSandbox);
  879.           break;
  880.         }
  881.       }
  882.       case "bookmarks":
  883.       default: {
  884.         var liveBookmarksMenuItem = this._document.getElementById("liveBookmarksMenuItem");
  885.         if (liveBookmarksMenuItem)
  886.           this._safeDoCommand(liveBookmarksMenuItem);
  887.       } 
  888.     }
  889.   },
  890.  
  891.   _initSubscriptionUI: function FW__initSubscriptionUI() {
  892.     var handlersMenuPopup = this._document.getElementById("handlersMenuPopup");
  893.     if (!handlersMenuPopup)
  894.       return;
  895.  
  896.     var feedType = this._getFeedType();
  897.     var codeStr;
  898.  
  899.     // change the background
  900.     var header = this._document.getElementById("feedHeader");
  901.     this._contentSandbox.header = header;
  902.     switch (feedType) {
  903.       case Ci.nsIFeed.TYPE_VIDEO:
  904.         codeStr = "header.className = 'videoPodcastBackground'; ";
  905.         break;
  906.  
  907.       case Ci.nsIFeed.TYPE_AUDIO:
  908.         codeStr = "header.className = 'audioPodcastBackground'; ";
  909.         break;
  910.  
  911.       default:
  912.         codeStr = "header.className = 'feedBackground'; ";
  913.     }
  914.  
  915.  
  916.     // Last-selected application
  917.     var menuItem = this._document.createElementNS(XUL_NS, "menuitem");
  918.     menuItem.id = "selectedAppMenuItem";
  919.     menuItem.className = "menuitem-iconic";
  920.     menuItem.setAttribute("handlerType", "client");
  921.     try {
  922.       var prefs = Cc["@mozilla.org/preferences-service;1"].
  923.                   getService(Ci.nsIPrefBranch);
  924.       this._selectedApp = prefs.getComplexValue(getPrefAppForType(feedType),
  925.                                                 Ci.nsILocalFile);
  926.  
  927.       if (this._selectedApp.exists())
  928.         this._initMenuItemWithFile(menuItem, this._selectedApp);
  929.       else {
  930.         // Hide the menuitem if the last selected application doesn't exist
  931.         menuItem.setAttribute("hidden", true);
  932.       }
  933.     }
  934.     catch(ex) {
  935.       // Hide the menuitem until an application is selected
  936.       menuItem.setAttribute("hidden", true);
  937.     }
  938.     this._contentSandbox.handlersMenuPopup = handlersMenuPopup;
  939.     this._contentSandbox.selectedAppMenuItem = menuItem;
  940.     
  941.     codeStr += "handlersMenuPopup.appendChild(selectedAppMenuItem); ";
  942.  
  943.     // List the default feed reader
  944.     try {
  945.       this._defaultSystemReader = Cc["@mozilla.org/browser/shell-service;1"].
  946.                                   getService(Ci.nsIShellService).
  947.                                   defaultFeedReader;
  948.       menuItem = this._document.createElementNS(XUL_NS, "menuitem");
  949.       menuItem.id = "defaultHandlerMenuItem";
  950.       menuItem.className = "menuitem-iconic";
  951.       menuItem.setAttribute("handlerType", "client");
  952.  
  953.       this._initMenuItemWithFile(menuItem, this._defaultSystemReader);
  954.  
  955.       // Hide the default reader item if it points to the same application
  956.       // as the last-selected application
  957.       if (this._selectedApp &&
  958.           this._selectedApp.path == this._defaultSystemReader.path)
  959.         menuItem.hidden = true;
  960.     }
  961.     catch(ex) { menuItem = null; /* no default reader */ }
  962.  
  963.     if (menuItem) {
  964.       this._contentSandbox.defaultHandlerMenuItem = menuItem;
  965.       codeStr += "handlersMenuPopup.appendChild(defaultHandlerMenuItem); ";
  966.     }
  967.  
  968.     // "Choose Application..." menuitem
  969.     menuItem = this._document.createElementNS(XUL_NS, "menuitem");
  970.     menuItem.id = "chooseApplicationMenuItem";
  971.     menuItem.className = "menuitem-iconic";
  972.     menuItem.setAttribute("label", this._getString("chooseApplicationMenuItem"));
  973.  
  974.     this._contentSandbox.chooseAppMenuItem = menuItem;
  975.     codeStr += "handlersMenuPopup.appendChild(chooseAppMenuItem); ";
  976.  
  977.     // separator
  978.     this._contentSandbox.chooseAppSep =
  979.       this._document.createElementNS(XUL_NS, "menuseparator")
  980.     codeStr += "handlersMenuPopup.appendChild(chooseAppSep); ";
  981.  
  982.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  983.  
  984.     var historySvc = Cc["@mozilla.org/browser/nav-history-service;1"].
  985.                      getService(Ci.nsINavHistoryService);
  986.     historySvc.addObserver(this, false);
  987.  
  988.     // List of web handlers
  989.     var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
  990.                getService(Ci.nsIWebContentConverterService);
  991.     var handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType), {});
  992.     if (handlers.length != 0) {
  993.       for (var i = 0; i < handlers.length; ++i) {
  994.         menuItem = this._document.createElementNS(XUL_NS, "menuitem");
  995.         menuItem.className = "menuitem-iconic";
  996.         menuItem.setAttribute("label", handlers[i].name);
  997.         menuItem.setAttribute("handlerType", "web");
  998.         menuItem.setAttribute("webhandlerurl", handlers[i].uri);
  999.         this._contentSandbox.menuItem = menuItem;
  1000.         codeStr = "handlersMenuPopup.appendChild(menuItem);";
  1001.         Cu.evalInSandbox(codeStr, this._contentSandbox);
  1002.  
  1003.         // For privacy reasons we cannot set the image attribute directly
  1004.         // to the icon url, see Bug 358878
  1005.         var uri = makeURI(handlers[i].uri);
  1006.         if (!this._setFaviconForWebReader(uri, menuItem)) {
  1007.           if (uri && /^https?/.test(uri.scheme)) {
  1008.             var iconURL = makeURI(uri.prePath + "/favicon.ico");
  1009.             this._faviconService.setAndLoadFaviconForPage(uri, iconURL, true);
  1010.           }
  1011.         }
  1012.       }
  1013.       this._contentSandbox.menuItem = null;
  1014.     }
  1015.  
  1016.     this._setSelectedHandler(feedType);
  1017.  
  1018.     // "Subscribe using..."
  1019.     this._setSubscribeUsingLabel();
  1020.  
  1021.     // "Always use..." checkbox initial state
  1022.     this._setAlwaysUseCheckedState(feedType);
  1023.     this._setAlwaysUseLabel();
  1024.  
  1025.     // We update the "Always use.." checkbox label whenever the selected item
  1026.     // in the list is changed
  1027.     handlersMenuPopup.addEventListener("command", this, false);
  1028.  
  1029.     // Set up the "Subscribe Now" button
  1030.     this._document
  1031.         .getElementById("subscribeButton")
  1032.         .addEventListener("command", this, false);
  1033.  
  1034.     // first-run ui
  1035.     var showFirstRunUI = true;
  1036.     try {
  1037.       showFirstRunUI = prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
  1038.     }
  1039.     catch (ex) { }
  1040.     if (showFirstRunUI) {
  1041.       var textfeedinfo1, textfeedinfo2;
  1042.       switch (feedType) {
  1043.         case Ci.nsIFeed.TYPE_VIDEO:
  1044.           textfeedinfo1 = "feedSubscriptionVideoPodcast1";
  1045.           textfeedinfo2 = "feedSubscriptionVideoPodcast2";
  1046.           break;
  1047.         case Ci.nsIFeed.TYPE_AUDIO:
  1048.           textfeedinfo1 = "feedSubscriptionAudioPodcast1";
  1049.           textfeedinfo2 = "feedSubscriptionAudioPodcast2";
  1050.           break;
  1051.         default:
  1052.           textfeedinfo1 = "feedSubscriptionFeed1";
  1053.           textfeedinfo2 = "feedSubscriptionFeed2";
  1054.       }
  1055.  
  1056.       this._contentSandbox.feedinfo1 =
  1057.         this._document.getElementById("feedSubscriptionInfo1");
  1058.       this._contentSandbox.feedinfo1Str = this._getString(textfeedinfo1);
  1059.       this._contentSandbox.feedinfo2 =
  1060.         this._document.getElementById("feedSubscriptionInfo2");
  1061.       this._contentSandbox.feedinfo2Str = this._getString(textfeedinfo2);
  1062.       this._contentSandbox.header = header;
  1063.       codeStr = "feedinfo1.textContent = feedinfo1Str; " +
  1064.                 "feedinfo2.textContent = feedinfo2Str; " +
  1065.                 "header.setAttribute('firstrun', 'true');"
  1066.       Cu.evalInSandbox(codeStr, this._contentSandbox);
  1067.       prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
  1068.     }
  1069.   },
  1070.  
  1071.   /**
  1072.    * Returns the original URI object of the feed and ensures that this
  1073.    * component is only ever invoked from the preview document.  
  1074.    * @param aWindow 
  1075.    *        The window of the document invoking the BrowserFeedWriter
  1076.    */
  1077.   _getOriginalURI: function FW__getOriginalURI(aWindow) {
  1078.     var chan = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
  1079.                getInterface(Ci.nsIWebNavigation).
  1080.                QueryInterface(Ci.nsIDocShell).currentDocumentChannel;
  1081.  
  1082.     var uri = makeURI(SUBSCRIBE_PAGE_URI);
  1083.     var resolvedURI = Cc["@mozilla.org/chrome/chrome-registry;1"].
  1084.                       getService(Ci.nsIChromeRegistry).
  1085.                       convertChromeURL(uri);
  1086.  
  1087.     if (resolvedURI.equals(chan.URI))
  1088.       return chan.originalURI;
  1089.  
  1090.     return null;
  1091.   },
  1092.  
  1093.   _window: null,
  1094.   _document: null,
  1095.   _feedURI: null,
  1096.   _feedPrincipal: null,
  1097.  
  1098.   // nsIFeedWriter
  1099.   init: function FW_init(aWindow) {
  1100.     // Explicitly wrap |window| in an XPCNativeWrapper to make sure
  1101.     // it's a real native object! This will throw an exception if we
  1102.     // get a non-native object.
  1103.     var window = new XPCNativeWrapper(aWindow);
  1104.     this._feedURI = this._getOriginalURI(window);
  1105.     if (!this._feedURI)
  1106.       return;
  1107.  
  1108.     this._window = window;
  1109.     this._document = window.document;
  1110.  
  1111.     var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
  1112.                  getService(Ci.nsIScriptSecurityManager);
  1113.     this._feedPrincipal = secman.getCodebasePrincipal(this._feedURI);
  1114.  
  1115.     LOG("Subscribe Preview: feed uri = " + this._window.location.href);
  1116.  
  1117.     // Set up the subscription UI
  1118.     this._initSubscriptionUI();
  1119.     var prefs = Cc["@mozilla.org/preferences-service;1"].
  1120.                 getService(Ci.nsIPrefBranch2);
  1121.     prefs.addObserver(PREF_SELECTED_ACTION, this, false);
  1122.     prefs.addObserver(PREF_SELECTED_READER, this, false);
  1123.     prefs.addObserver(PREF_SELECTED_WEB, this, false);
  1124.     prefs.addObserver(PREF_SELECTED_APP, this, false);
  1125.     prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, false);
  1126.     prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, false);
  1127.     prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, false);
  1128.     prefs.addObserver(PREF_VIDEO_SELECTED_APP, this, false);
  1129.  
  1130.     prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, false);
  1131.     prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, false);
  1132.     prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, false);
  1133.     prefs.addObserver(PREF_AUDIO_SELECTED_APP, this, false);
  1134.   },
  1135.  
  1136.   writeContent: function FW_writeContent() {
  1137.     if (!this._window)
  1138.       return;
  1139.  
  1140.     try {
  1141.       // Set up the feed content
  1142.       var container = this._getContainer();
  1143.       if (!container)
  1144.         return;
  1145.  
  1146.       this._setTitleText(container);
  1147.       this._setTitleImage(container);
  1148.       this._writeFeedContent(container);
  1149.     }
  1150.     finally {
  1151.       this._removeFeedFromCache();
  1152.     }
  1153.   },
  1154.  
  1155.   close: function FW_close() {
  1156.     this._document
  1157.         .getElementById("handlersMenuPopup")
  1158.         .removeEventListener("command", this, false);
  1159.     this._document
  1160.         .getElementById("subscribeButton")
  1161.         .removeEventListener("command", this, false);
  1162.     this._document = null;
  1163.     this._window = null;
  1164.     var prefs = Cc["@mozilla.org/preferences-service;1"].
  1165.                 getService(Ci.nsIPrefBranch2);
  1166.     prefs.removeObserver(PREF_SELECTED_ACTION, this);
  1167.     prefs.removeObserver(PREF_SELECTED_READER, this);
  1168.     prefs.removeObserver(PREF_SELECTED_WEB, this);
  1169.     prefs.removeObserver(PREF_SELECTED_APP, this);
  1170.     prefs.removeObserver(PREF_VIDEO_SELECTED_ACTION, this);
  1171.     prefs.removeObserver(PREF_VIDEO_SELECTED_READER, this);
  1172.     prefs.removeObserver(PREF_VIDEO_SELECTED_WEB, this);
  1173.     prefs.removeObserver(PREF_VIDEO_SELECTED_APP, this);
  1174.  
  1175.     prefs.removeObserver(PREF_AUDIO_SELECTED_ACTION, this);
  1176.     prefs.removeObserver(PREF_AUDIO_SELECTED_READER, this);
  1177.     prefs.removeObserver(PREF_AUDIO_SELECTED_WEB, this);
  1178.     prefs.removeObserver(PREF_AUDIO_SELECTED_APP, this);
  1179.  
  1180.     this._removeFeedFromCache();
  1181.     this.__faviconService = null;
  1182.     this.__bundle = null;
  1183.     this._feedURI = null;
  1184.     this.__contentSandbox = null;
  1185.  
  1186.     var historySvc = Cc["@mozilla.org/browser/nav-history-service;1"].
  1187.                      getService(Ci.nsINavHistoryService);
  1188.     historySvc.removeObserver(this);
  1189.   },
  1190.  
  1191.   _removeFeedFromCache: function FW__removeFeedFromCache() {
  1192.     if (this._feedURI) {
  1193.       var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
  1194.                         getService(Ci.nsIFeedResultService);
  1195.       feedService.removeFeedResult(this._feedURI);
  1196.       this._feedURI = null;
  1197.     }
  1198.   },
  1199.  
  1200.   subscribe: function FW_subscribe() {
  1201.     var feedType = this._getFeedType();
  1202.  
  1203.     // Subscribe to the feed using the selected handler and save prefs
  1204.     var prefs = Cc["@mozilla.org/preferences-service;1"].
  1205.                 getService(Ci.nsIPrefBranch);
  1206.     var defaultHandler = "reader";
  1207.     var useAsDefault = this._document.getElementById("alwaysUse")
  1208.                                      .getAttribute("checked");
  1209.  
  1210.     var handlersMenuList = this._document.getElementById("handlersMenuList");
  1211.     var selectedItem = this._getSelectedItemFromMenulist(handlersMenuList);
  1212.  
  1213.     // Show the file picker before subscribing if the
  1214.     // choose application menuitem was choosen using the keyboard
  1215.     if (selectedItem.id == "chooseApplicationMenuItem") {
  1216.       if (!this._chooseClientApp())
  1217.         return;
  1218.       
  1219.       selectedItem = this._getSelectedItemFromMenulist(handlersMenuList);
  1220.     }
  1221.  
  1222.     if (selectedItem.hasAttribute("webhandlerurl")) {
  1223.       var webURI = selectedItem.getAttribute("webhandlerurl");
  1224.       prefs.setCharPref(getPrefReaderForType(feedType), "web");
  1225.  
  1226.       var supportsString = Cc["@mozilla.org/supports-string;1"].
  1227.                            createInstance(Ci.nsISupportsString);
  1228.       supportsString.data = webURI;
  1229.       prefs.setComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString,
  1230.                             supportsString);
  1231.  
  1232.       var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
  1233.                  getService(Ci.nsIWebContentConverterService);
  1234.       var handler = wccr.getWebContentHandlerByURI(this._getMimeTypeForFeedType(feedType), webURI);
  1235.       if (handler) {
  1236.         if (useAsDefault)
  1237.           wccr.setAutoHandler(this._getMimeTypeForFeedType(feedType), handler);
  1238.  
  1239.         this._window.location.href = handler.getHandlerURI(this._window.location.href);
  1240.       }
  1241.     }
  1242.     else {
  1243.       switch (selectedItem.id) {
  1244.         case "selectedAppMenuItem":
  1245.           prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile, 
  1246.                                 this._selectedApp);
  1247.           prefs.setCharPref(getPrefReaderForType(feedType), "client");
  1248.           break;
  1249.         case "defaultHandlerMenuItem":
  1250.           prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile, 
  1251.                                 this._defaultSystemReader);
  1252.           prefs.setCharPref(getPrefReaderForType(feedType), "client");
  1253.           break;
  1254.         case "liveBookmarksMenuItem":
  1255.           defaultHandler = "bookmarks";
  1256.           prefs.setCharPref(getPrefReaderForType(feedType), "bookmarks");
  1257.           break;
  1258.       }
  1259.       var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
  1260.                         getService(Ci.nsIFeedResultService);
  1261.  
  1262.       // Pull the title and subtitle out of the document
  1263.       var feedTitle = this._document.getElementById(TITLE_ID).textContent;
  1264.       var feedSubtitle = this._document.getElementById(SUBTITLE_ID).textContent;
  1265.       feedService.addToClientReader(this._window.location.href, feedTitle, feedSubtitle, feedType);
  1266.     }
  1267.  
  1268.     // If "Always use..." is checked, we should set PREF_*SELECTED_ACTION
  1269.     // to either "reader" (If a web reader or if an application is selected),
  1270.     // or to "bookmarks" (if the live bookmarks option is selected).
  1271.     // Otherwise, we should set it to "ask"
  1272.     if (useAsDefault)
  1273.       prefs.setCharPref(getPrefActionForType(feedType), defaultHandler);
  1274.     else
  1275.       prefs.setCharPref(getPrefActionForType(feedType), "ask");
  1276.   },
  1277.  
  1278.   // nsIObserver
  1279.   observe: function FW_observe(subject, topic, data) {
  1280.     // see init()
  1281.     subject = new XPCNativeWrapper(subject);
  1282.     
  1283.     if (!this._window) {
  1284.       // this._window is null unless this.init was called with a trusted
  1285.       // window object.
  1286.       return;
  1287.     }
  1288.  
  1289.     var feedType = this._getFeedType();
  1290.  
  1291.     if (topic == "nsPref:changed") {
  1292.       switch (data) {
  1293.         case PREF_SELECTED_READER:
  1294.         case PREF_SELECTED_WEB:
  1295.         case PREF_SELECTED_APP:
  1296.         case PREF_VIDEO_SELECTED_READER:
  1297.         case PREF_VIDEO_SELECTED_WEB:
  1298.         case PREF_VIDEO_SELECTED_APP:
  1299.         case PREF_AUDIO_SELECTED_READER:
  1300.         case PREF_AUDIO_SELECTED_WEB:
  1301.         case PREF_AUDIO_SELECTED_APP:
  1302.           this._setSelectedHandler(feedType);
  1303.           break;
  1304.         case PREF_SELECTED_ACTION:
  1305.         case PREF_VIDEO_SELECTED_ACTION:
  1306.         case PREF_AUDIO_SELECTED_ACTION:
  1307.           this._setAlwaysUseCheckedState(feedType);
  1308.       }
  1309.     } 
  1310.   },
  1311.  
  1312.   /**
  1313.    * Sets the icon for the given web-reader item in the readers menu
  1314.    * if the favicon-service has the necessary icon stored.
  1315.    * @param aURI
  1316.    *        the reader URI.
  1317.    * @param aMenuItem
  1318.    *        the reader item in the readers menulist.
  1319.    * @return true if the icon was set, false otherwise.
  1320.    */
  1321.   _setFaviconForWebReader:
  1322.   function FW__setFaviconForWebReader(aURI, aMenuItem) {
  1323.     var faviconsSvc = this._faviconService;
  1324.     var faviconURI = null;
  1325.     try {
  1326.       faviconURI = faviconsSvc.getFaviconForPage(aURI);
  1327.     }
  1328.     catch(ex) { }
  1329.  
  1330.     if (faviconURI) {
  1331.       var dataURL = faviconsSvc.getFaviconDataAsDataURL(faviconURI);
  1332.       if (dataURL) {
  1333.         this._contentSandbox.menuItem = aMenuItem;
  1334.         this._contentSandbox.dataURL = dataURL;
  1335.         var codeStr = "menuItem.setAttribute('image', dataURL);";
  1336.         Cu.evalInSandbox(codeStr, this._contentSandbox);
  1337.         this._contentSandbox.menuItem = null;
  1338.         this._contentSandbox.dataURL = null;
  1339.  
  1340.         return true;
  1341.       }
  1342.     }
  1343.  
  1344.     return false;
  1345.   },
  1346.  
  1347.    // nsINavHistoryService
  1348.    onPageChanged: function FW_onPageChanged(aURI, aWhat, aValue) {
  1349.      // see init()
  1350.      aURI = new XPCNativeWrapper(aURI);
  1351.  
  1352.      if (aWhat == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
  1353.        // Go through the readers menu and look for the corresponding
  1354.        // reader menu-item for the page if any.
  1355.        var spec = aURI.spec;
  1356.        var handlersMenulist = this._document.getElementById("handlersMenuList");
  1357.        var possibleHandlers = handlersMenulist.firstChild.childNodes;
  1358.        for (var i=0; i < possibleHandlers.length ; i++) {
  1359.          if (possibleHandlers[i].getAttribute("webhandlerurl") == spec) {
  1360.            this._setFaviconForWebReader(aURI, possibleHandlers[i]);
  1361.            return;
  1362.          }
  1363.        }
  1364.      }
  1365.    },
  1366.  
  1367.    onBeginUpdateBatch: function() { },
  1368.    onEndUpdateBatch: function() { },
  1369.    onVisit: function() { },
  1370.    onTitleChanged: function() { },
  1371.    onDeleteURI: function() { },
  1372.    onClearHistory: function() { },
  1373.    onPageExpired: function() { },
  1374.  
  1375.   // nsIClassInfo
  1376.   getInterfaces: function FW_getInterfaces(countRef) {
  1377.     var interfaces = [Ci.nsIFeedWriter, Ci.nsIClassInfo, Ci.nsISupports];
  1378.     countRef.value = interfaces.length;
  1379.     return interfaces;
  1380.   },
  1381.   getHelperForLanguage: function FW_getHelperForLanguage(language) null,
  1382.   contractID: "@mozilla.org/browser/feeds/result-writer;1",
  1383.   classDescription: "Feed Writer",
  1384.   classID: Components.ID("{49bb6593-3aff-4eb3-a068-2712c28bd58e}"),
  1385.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  1386.   flags: Ci.nsIClassInfo.DOM_OBJECT,
  1387.   _xpcom_categories: [{ category: "JavaScript global constructor",
  1388.                         entry: "BrowserFeedWriter"}],
  1389.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeedWriter, Ci.nsIClassInfo,
  1390.                                          Ci.nsIDOMEventListener, Ci.nsIObserver,
  1391.                                          Ci.nsINavHistoryObserver])
  1392. };
  1393.  
  1394. function NSGetModule(cm, file)
  1395.   XPCOMUtils.generateModule([FeedWriter]);
  1396.